<?php

/*
 * This script belongs to Aspect PHP.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This script is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-
 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details.
 */

/**
 * The implementation for the aspectJoinpoint contract
 *
 * @package    aspectPhp
 * @subpackage lib
 * @author     gboisse
 */
class aspectJoinpointImpl implements aspectJoinpoint {

    /** @var string $className The targeted class name */
    protected $className;
    /** @var string $methodName The called method name */
    protected $methodName;
    /** @var array $arguments The arguments sorted by parameter name */
    protected $arguments;
    /** @var mixed $methodArguments The arguments passed to the method */
    protected $methodArguments;
    /** @var mixed The result of the execution of the proxified method */
    protected $result;
    /** @var Exception The exception thrown by the execution of the proxified method */
    protected $exception = null;
    /** @var boolean Is setted to true after setResult() is called */
    private $afterContext = false;

    /**
     * Creates a new joinpoint
     *
     * @param string $className The targeted class name
     * @param string $methodName The called method name
     * @param array $arguments The arguments passed to the method
     */
    public function __construct($className, $methodName, $arguments) {
        $this->className = $className;
        $this->methodName = $methodName;

        $method = new ReflectionMethod($className, $methodName);
        $parameters = $method->getParameters();
        foreach ($parameters as $key => $parameter) {
            $sortedArguments[$key] = $arguments[$key];
            $sortedArguments[$parameter->getName()] = $arguments[$key];
        }
        $this->arguments = $sortedArguments;
        $this->methodArguments = $arguments;
    }

    /**
     * Returns the name of the target class this join point refers to
     *
     * @return string The name of the targeted class
     */
    public function getClassName() {

        return $this->className;
    }

    /**
     * Returns the name of the method this join point refers to
     *
     * @return string The name of the target method
     */
    public function getMethodName() {

        return $this->methodName;
    }

    /**
     * Returns an array of arguments sorted by parameter name
     *
     * @return array Array of arguments
     */
    public function getMethodArguments() {

        return $this->arguments;
    }

    /**
     * Returns an array of arguments which have been passed to the target method
     *
     * @return array Array of arguments
     */
    public function getMethodArgumentsForInvoke() {

        return $this->methodArguments;
    }

    /**
     * Returns the value of the specified method argument
     *
     * @param  string $argumentName Name of the argument
     * @return mixed Value of the argument
     */
    public function getMethodArgument($argumentName) {

        return $this->arguments[$argumentName];
    }

    /**
     * Returns true if the argument with the specified name exists in the
     * method call this joinpoint refers to
     *
     * @param string $argumentName Name of the argument to check
     * @return boolean Returns true if the argument exists
     */
    public function hasMethodArgument($argumentName) {

        return isset($this->arguments[$argumentName]);
    }

    /**
     * Returns the result of the method invocation. The result is only
     * available for afterReturning advices
     *
     * @return mixed Result of the method invocation
     */
    public function getResult() {
        if (!$this->afterContext) {
            throw new LogicException("Cannot access to the result of $this->methodName() from $this->className before method execution!");
        }

        return $this->result;
    }

    /**
     * Sets the value of the result after the method is called
     *
     * @param mixed $result The result of the method call
     */
    public function setResult($result) {

        $this->afterContext = true;
        $this->result = $result;
    }

    /**
     * If an exception was thrown by the target method.
     * Only makes sense for afterThrowing advices
     *
     * @return boolean
     */
    public function hasException() {
        if (!$this->afterContext) {
            throw new LogicException("Cannot access to the result of $this->methodName() from $this->className before method execution!");
        }

        return (!is_null($this->exception));
    }

    /**
     * Returns the exception which has been thrown in the target method.
     * If no exception has been thrown, null is returned.
     * Only makes sense for afterThrowing advices
     *
     * @return Exception The exception thrown or null
     */
    public function getException() {
        if (!$this->afterContext) {
            throw new LogicException("Cannot access to the result of $this->methodName() from $this->className before method execution!");
        }

        return $this->exception;
    }

    /**
     * Sets the exception thrown by the method call
     *
     * @param Exception $exception The exception that has been thrown
     */
    public function setException(Exception $exception) {

        $this->afterContext = true;
        $this->exception = $exception;
    }

}
